#include "vcl_stl.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "h_base.h"
#include "h_xml.h"
using namespace h_base;

#include "simbat.h"
using namespace simbat;

#include "bkbase.h"
#include "bstcp.h"

static char *SimbatBase = NULL;
static char ErrMsg[8192 + 1];

static HString SyslogDir;
static HSyslog Syslog;
static int LocalPort;
static HString ShrDir;
static HString LogDir;
static HString SelfAddr;
static bool HotbackEnableFlag = false;              // 是否启动热备
static bool HotbackPausedFlag = false;              // 是否暂停热备
static HString HotbackAddr;                         // 热备机IP地址
static int HotbackPort = -1;                        // 热备机端口
static map<HString, HBackupInfo> BackupMap;         // 若干备份集群

static void LoadFromCfg(HXmlTree &Cfg)
{
   SyslogDir = EnvEvaluate(Cfg.GetValue("SYSLOG_DIR", "$SIMBAT_BASE/etc"));
   Syslog.Setup(SyslogDir, Cfg.GetValueUpper("SYSLOG_LEVEL", "INFO"));
   LocalPort = StrToInt(Cfg.GetValue("PORT").c_str());
   ShrDir = EnvEvaluate(Cfg.GetValue("SHR_DIR", "$SIMBAT_BASE/shr"));
   LogDir = EnvEvaluate(Cfg.GetValue("LOG_DIR", "$SIMBAT_BASE/log"));
   setenv("SIMBAT_SHR_DIR", ShrDir.c_str(), 1);
   setenv("SIMBAT_LOG_DIR", LogDir.c_str(), 1);

   char TmpStr[500 + 1];

   snprintf(TmpStr, 500, "%s:%d", "127.0.0.1", LocalPort);
   SelfAddr = TmpStr;

   {
      HotbackEnableFlag = ( Cfg.GetValueUpper("HOTBACK_ENABLE", "FALSE") == "TRUE" );
      HotbackPausedFlag = ( Cfg.GetValueUpper("HOTBACK_PAUSED", "FALSE") == "TRUE" );
      HString AddrStr = Cfg.GetValue("HOTBACK_ADDR");
      if ( AddrStr == "" )
      {
         HotbackEnableFlag = false;
         HotbackAddr = "";
         HotbackPort = -1;
      }
      else
      {
         vector<HString> AddrPort;
         SplitString(AddrStr.c_str(), ':', AddrPort, true);
         if ( AddrPort.size() == 2 )
         {
            HotbackAddr = AddrPort[0];
            HotbackPort = StrToInt(AddrPort[1].c_str());
         }
         else
         {
            HotbackEnableFlag = false;
            HotbackAddr = "";
            HotbackPort = -1;
         }
      }
   }

   LoadBackupInfoFromCfg(Syslog, Cfg, BackupMap);
}

static bool LoadCfg(HString &ErrStr)
{
   char FileName[500 + 1];
   HXmlTree Cfg;

   {
      HFileLockRead Lock("$SIMBAT_BASE/tmp/config.lck");
      snprintf(FileName, 500, "%s/etc/config.xml", SimbatBase);
      if ( !Cfg.LoadFromFile(FileName) )
      {
         ErrStr = Cfg.GetErrorString();
         return false;
      }
   }

   LoadFromCfg(Cfg);
   return true;
}

static int FireEvent(const char *HostName, const char *AEvent,
   const char *APort);
static int WaitEvent(const char *AEvent, const char *AFileName,
   const char *ATime, const char *ACommand);

static int FireEvent(const char *HostName, const char *Event)
{
   snprintf(ErrMsg, 8192, "FireEvent(HostName = %s, Event = %s, LocalPort = %d)",
      HostName, Event, LocalPort);
   Syslog.Log(__FILE__, __LINE__, llInfo, "ENTRY", ErrMsg);
   vector<HString> HostVec;
   SplitString(HostName, ',', HostVec);

   int i;

   for ( i = 0; i < HostVec.size(); i++ )
   {
      HString &Host = HostVec[i];
      HString Result;
      char Buf[500 + 1];
      char AddrStr[500 + 1];
      snprintf(Buf, 500, "C|||%s", Event);
      if ( strchr(Host.c_str(), ':') == NULL )
      {
         snprintf(AddrStr, 500, "%s:%d", Host.c_str(), LocalPort);
      }
      else
      {
         BkStrNCpy(AddrStr, Host.c_str(), sizeof(AddrStr) - 1);
      }
      bool r = CallServer(Syslog, 60000, AddrStr, "FIRE_EVENT", Event, Buf, true, Result);
      if ( r && Result == "0" )
      {
         break;
      }
   }

   if ( i == HostVec.size() )
   {
      snprintf(ErrMsg, 8192, "FireEvent(HostName = %s, Event = %s, LocalPort = %d) fail",
         HostName, Event, LocalPort);
      fprintf(stderr, "%s\n", ErrMsg);
      Syslog.Log(__FILE__, __LINE__, llInfo, "EXIT", "Process fail");
      return 8;
   }

   Syslog.Log(__FILE__, __LINE__, llInfo, "EXIT", "Process completed");
   return 0;
}

static int WaitEvent(const char *AEvent, const char *AFileName,
   const char *ATime, const char *ACommand)
{
   snprintf(ErrMsg, 8192, "WaitEvent(Event = %s, File = %s, Time = %s, Command = %s)", AEvent, AFileName, ATime, ACommand);
   Syslog.Log(__FILE__, __LINE__, llInfo, "ENTRY", ErrMsg);

   char Event[32 + 1];
   HString EventFileName;
   HString SocketName;
   struct stat Stat;

   fprintf(stderr, "WaitEvent(\n"
      "   EVENT = %s,\n"
      "   FILE_NAME = %s,\n"
      "   TIME = %s,\n"
      "   COMMAND = %s)\n",
      AEvent, AFileName, ATime, ACommand);
   memset(Event, 0, 32 + 1);
   strncpy(Event, AEvent, 32);
   Event[32] = '\0';
   BkToUpper(Event);
   EventFileName = ShrDir + "/events/" + Event + ".file";
   SocketName = EnvEvaluate("$SIMBAT_BASE/tmp/") + Event + ".sock";
   int Sock = -1;
   int MsgSock = -1;
   time_t WaitTime;
   bool CommandFlag = false;

   if ( AEvent[0] != '\0' )
   {
      unlink(SocketName.c_str());
      Sock = BsCreateServerSocketUD(SocketName.c_str(), 5);
      if ( Sock < 0 )
      {
         snprintf(ErrMsg, 8192, "BsCreateServerSocketUD '%s' fail, r = %d, errno = %d", SocketName.c_str(), Sock, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         BkPrintError("%s", ErrMsg);
         return 8;
      }
   }

   if ( ATime[0] != '\0' )
   {
      time_t CurrTime = time(NULL);
      if ( strchr(ATime, ':') == NULL )
      {
         WaitTime = CurrTime + StrToInt(ATime);
      }
      else
      {
         WaitTime = StringToTime(ATime);
      }
   }

   for ( ; ; )
   {
      if ( AEvent[0] != '\0' )
      {
         BkPrintError("stat(%s)", EventFileName.c_str());
         if ( stat(EventFileName.c_str(), &Stat) == -1 )
         {
            if ( errno != ENOENT )
            {
               snprintf(ErrMsg, 8192, "Stat %s fail, errno = %d",
                  EventFileName.c_str(), errno);
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
               BkPrintError("%s", ErrMsg);
               return 8;
            }
         }
         else
         {
            snprintf(ErrMsg, 8192, "Wait for event complete");
            Syslog.Log(__FILE__, __LINE__, llInfo, "EXIT", ErrMsg);
            break;
         }
      }
      if ( AFileName[0] != '\0' )
      {
         BkPrintError("stat(%s)", AFileName);
         if ( stat(AFileName, &Stat) == -1 )
         {
            if ( errno != ENOENT )
            {
               snprintf(ErrMsg, 8192, "Stat %s fail, errno = %d\n", AFileName, errno);
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
               BkPrintError("%s", ErrMsg);
               return 8;
            }
         }
         else
         {
            snprintf(ErrMsg, 8192, "Wait for file complete");
            Syslog.Log(__FILE__, __LINE__, llInfo, "EXIT", ErrMsg);
            break;
         }
      }
      int StepTime = 30;
      if ( ATime[0] != '\0' )
      {
         int Delta = WaitTime - time(NULL);
         if ( Delta < 0 )
         {
            snprintf(ErrMsg, 8192, "Wait for time complete");
            Syslog.Log(__FILE__, __LINE__, llInfo, "EXIT", ErrMsg);
            break;
         }
         if ( Delta < StepTime )
         {
            StepTime = Delta;
         }
      }
      if ( ACommand[0] != '\0' )
      {
         if ( !CommandFlag )
         {
            pid_t Pid = fork();
            if ( Pid == -1 )
            {
               snprintf(ErrMsg, 8192, "fork fail, errno = %d", errno);
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
               BkPrintError("%s:%d %s", __FILE__, __LINE__, ErrMsg);
               return 8;
            }
            if ( Pid == 0 )
            {
               int r = system(ACommand);
               if ( r == -1 || !WIFEXITED(r) || WEXITSTATUS(r) != 0 )
               {
                  exit(8);
               }
               else
               {
                  exit(0);
               }
            }
            CommandFlag = true;
         }
         else
         {
            int r2;
            int r = waitpid(-1, &r2, WNOHANG);
            if ( r == -1 )
            {
               snprintf(ErrMsg, 8192, "waitpid fail, errno = %d", errno);
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
               BkPrintError("%s", ErrMsg);
               return 8;
            }
            if ( r != 0 )
            {
               CommandFlag = false;
               if ( WIFEXITED(r2) && WEXITSTATUS(r2) == 0 )
               {
                  snprintf(ErrMsg, 8192, "Wait for command complete");
                  Syslog.Log(__FILE__, __LINE__, llInfo, "EXIT", ErrMsg);
                  break;
               }
            }
         }
      }
      if ( AEvent[0] != '\0' )
      {
         MsgSock = BsAcceptConnectionUD(Sock, NULL, StepTime * 1000);
         if ( MsgSock < 0 && MsgSock != BK_ETIMEOUT )
         {
            snprintf(ErrMsg, 8192, "BsAcceptConnection fail, r = %d, errno = %d", MsgSock, errno);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            BkPrintError("%s", ErrMsg);
            return 8;
         }
         if ( MsgSock == BK_ETIMEOUT )
         {
            continue;
         }
         snprintf(ErrMsg, 8192, "Wait for event complete");
         Syslog.Log(__FILE__, __LINE__, llInfo, "EXIT", ErrMsg);
         break;
      }
      else
      {
         sleep(StepTime);
      }
   }

   if ( AEvent[0] != '\0' )
   {
      if ( MsgSock >= 0 )
      {
         close(MsgSock);
      }
      if ( MsgSock >= 0 )
      {
         close(Sock);
      }
      unlink(SocketName.c_str());
      unlink(EventFileName.c_str());
      HString ErrStr;
      LoadCfg(ErrStr);
      if ( HotbackEnableFlag && !HotbackPausedFlag )
      {
         if ( !SendBackupFile(Syslog, ShrDir, LogDir, SimbatBase, HotbackAddr.c_str(), HotbackPort, boEvent, Event, 0, -1) )
         {
            snprintf(ErrMsg, 8192, "SendBackupFile(%s, %d, %s) fail", HotbackAddr.c_str(), HotbackPort, Event);
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         }
      }
      for ( map<HString, HBackupInfo>::iterator Iter = BackupMap.begin();
            Iter != BackupMap.end(); Iter++ )
      {
         HBackupInfo &Backup = Iter->second;
         if ( Backup.EnableFlag && !Backup.PausedFlag )
         {
            if ( !SendBackupFile(Syslog, ShrDir, LogDir, SimbatBase, Backup.Addr.c_str(), Backup.Port, boEvent, Event, 0, -1) )
            {
               snprintf(ErrMsg, 8192, "SendBackupFile(%s, %d, %s) fail", Backup.Addr.c_str(), Backup.Port, Event);
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            }
         }
      }
   }

   return 0;
}

void PrintUsage()
{
   fprintf(stderr, "Usage : [PATH/]simbat_evnt fire hostaddr event\n");
   fprintf(stderr, "        [PATH/]simbat_evnt wait event file_name time command\n");
}

int main(int argc, char *argv[])
{
   if ( strchr(argv[0], '/') != NULL )
   {
      SetBaseByPath(argv[0]);
   }

   SimbatBase = getenv("SIMBAT_BASE");

   if ( SimbatBase == NULL )
   {
      fprintf(stderr, "$SIMBAT_BASE not defined\n");
      return 8;
   }

   HString ErrStr;

   if ( !LoadCfg(ErrStr) )
   {
      BkPrintError("Load config.xml fail, %s", ErrStr.c_str());
      PrintUsage();
      return 8;
   }

   if ( argc == 1 ||
       strcmp(argv[1], "fire") != 0 && strcmp(argv[1], "wait") != 0 ||
       strcmp(argv[1], "fire") == 0 && argc != 4 ||
       strcmp(argv[1], "wait") == 0 && argc != 6 )
   {
      PrintUsage();
      return 8;
   }

   char *Event;

   if ( strcmp(argv[1], "fire") == 0 )
   {
      Event = argv[3];
   }
   else
   {
      Event = argv[2];
   }

   for ( int i = 0; Event[i] != '\0'; i++ )
   {
      char c = Event[i];
      if ( !isalnum(c) && c != '_' && c != '-' )
      {
         fprintf(stderr, "Invalid event '%s'\n", Event);
         return 8;
      }
   }

   if ( strcmp(argv[1], "fire") == 0 )
   {
      return FireEvent(argv[2], argv[3]);
   }
   else
   {
      return WaitEvent(argv[2], argv[3], argv[4], argv[5]);
   }
}
